package com.vv.tools.common.util;

import com.vv.tools.common.config.HttpClientProperties;
import com.vv.tools.common.message.CustomerBizException;

import jakarta.annotation.Resource;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.MapUtils;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import static com.vv.tools.common.message.ResultEnum.HTTP_CLIENT_EXCEPTION;

/**
 * @author wen.li
 */
@Slf4j
@Component
public class HttpClient {

    private static final ScheduledExecutorService SCHEDULED_EXECUTOR = Executors.newScheduledThreadPool(1);

    private CloseableHttpClient client;

    @Resource
    private HttpClientProperties httpClientProperties;

    public String execute(String httpMethod, String url, Map<String, String> headerMap, Object requestBody) {
        CloseableHttpClient httpClient = getHttpClient(httpClientProperties);
        try {
            ClassicHttpRequest httpUriRequest = toHttpUriRequest(httpMethod, url, headerMap, requestBody);
            log.info("{} {} header:{}, requestBody:{}", httpMethod, url, headerMap, requestBody);
            return httpClient.execute(httpUriRequest, new BasicHttpClientResponseHandler());
        } catch (URISyntaxException | IOException e) {
            throw new CustomerBizException(HTTP_CLIENT_EXCEPTION, e);
        }
    }

    private ClassicHttpRequest toHttpUriRequest(String httpMethod, String url, Map<String, String> headerMap, Object requestBody)
        throws URISyntaxException {

        ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.create(httpMethod);

        // 构建url
        URI uri = new URIBuilder(url).build();
        requestBuilder.setUri(uri.getScheme() + "://" + uri.getAuthority() + uri.getRawPath());

        //request query params
        UriComponentsBuilder.fromUri(uri).build().getQueryParams().toSingleValueMap().forEach(requestBuilder::addParameter);

        //request headers
        if (MapUtils.isNotEmpty(headerMap)) {
            headerMap.forEach(requestBuilder::addHeader);
        }

        //request entity
        if (!HttpMethod.GET.matches(httpMethod) && null != requestBody) {
            HttpEntity httpEntity;
            Header mimeTypeHeader = requestBuilder.getFirstHeader("Content-Type");
            if (mimeTypeHeader != null) {
                ContentType mimeType = ContentType.create(mimeTypeHeader.getValue()).withCharset(StandardCharsets.UTF_8);
                httpEntity = new StringEntity((String) requestBody, mimeType);
            } else {
                httpEntity = new StringEntity((String) requestBody);
            }
            requestBuilder.setEntity(httpEntity);
        }

        // 构建request
        return requestBuilder.build();
    }

    private CloseableHttpClient getHttpClient(HttpClientProperties httpClientProperties) {
        if (null == httpClientProperties) {
            httpClientProperties = defaultConfig();
        }

        if (null == client) {
            // 创建请求配置
            RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(
                Timeout.ofMilliseconds(httpClientProperties.getConnectionRequestTimeout())).setResponseTimeout(
                Timeout.ofMilliseconds(httpClientProperties.getResponseTimeout())).build();

            // 创建Socket配置
            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(httpClientProperties.getReadTimeout())).build();

            // 创建连接管理器
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
            connectionManager.setMaxTotal(httpClientProperties.getMaxTotal());
            connectionManager.setDefaultMaxPerRoute(httpClientProperties.getMaxPerRoute());
            connectionManager.setDefaultSocketConfig(socketConfig);

            // 创建HttpClientBuilder
            HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager)
                .setConnectionManagerShared(true).setConnectionReuseStrategy(new DefaultConnectionReuseStrategy());

            // 构建HttpClient
            client = builder.build();

            // 打印连接池
            SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> {
                connectionManager.closeExpired();
                connectionManager.closeIdle(TimeValue.ofSeconds(10));
            }, 10, 10, TimeUnit.SECONDS);
        }
        return client;
    }

    private HttpClientProperties defaultConfig() {
        HttpClientProperties httpClientConfig = new HttpClientProperties();
        httpClientConfig.setMaxTotal(100);
        httpClientConfig.setMaxPerRoute(50);
        httpClientConfig.setReadTimeout(30000);
        httpClientConfig.setConnectionRequestTimeout(30000);
        httpClientConfig.setResponseTimeout(10000);
        return httpClientConfig;
    }

}
